package com.ruoyi.system.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.HttpUtils;
import com.ruoyi.system.domain.BKApiDTO;
import com.ruoyi.system.domain.BKStudent;
import com.ruoyi.system.domain.ClassDTO;
import com.ruoyi.system.domain.StudentStatusDTO;
import com.ruoyi.system.domain.MajorDTO;
import com.ruoyi.system.mapper.BKStudentMapper;
import com.ruoyi.system.service.BKService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;

import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.Data;
import java.util.Map;
import java.util.stream.Collectors;

// 异常数据记录实体（内部类）
@Data
class DataConvertError {
    private String stuSno; // 学号（唯一标识）
    private String fieldName; // 异常字段名
    private String fieldValue; // 异常字段值
    private String errorReason; // 异常原因
}

/**
 * BK数据同步业务实现
 */
@Service
public class BKServiceImpl implements BKService {
    private static final Logger log = LoggerFactory.getLogger(BKServiceImpl.class);

    // API配置
    private static final String TOKEN_URL = "https://data.neau.edu.cn/open_api/authentication/get_access_token";
    private static final String DATA_URL = "https://data.neau.edu.cn/open_api/customization/view_hotel_delta/full";
    private static final String STUDENT_STATUS_URL = "https://data.neau.edu.cn/open_api/customization/view_hotel_charlie/full";
    private static final String MAJOR_URL = "https://data.neau.edu.cn/open_api/customization/view_hotel_foxtrot/full";
    private static final String CLASS_URL = "https://data.neau.edu.cn/open_api/customization/view_hotel_echo/full";
    private static final String KEY = "20250714773319445588170900848643464";
    private static final String SECRET = "8ad2a72d4de5c95786203c0552563601842014be";

    @Autowired
    private BKStudentMapper studentMapper;

    // Token缓存与过期时间
    private String token;
    private LocalDateTime tokenExpireTime;

    /**
     * 通用分页获取接口数据
     * 
     * @param token 访问令牌
     * @param url   接口URL
     * @param clazz DTO类对象
     * @return 所有页数据的列表
     */
    private <T> List<T> getAllApiData(String token, String url, Class<T> clazz) throws Exception {
        List<T> allData = new ArrayList<>();
        int page = 1;
        while (true) {
            String requestUrl = url + "?access_token=" + token + "&page=" + page;
            String resp = HttpUtils.sendGet(requestUrl);
            log.debug("第 {} 页数据接口响应：{}", page, resp);

            JSONObject json = JSONObject.parseObject(resp);
            if (!"10000".equals(json.getString("code"))) {
                throw new RuntimeException("第" + page + "页数据获取失败：" + json.getString("msg"));
            }

            JSONObject result = json.getJSONObject("result");
            JSONArray dataArr = result.getJSONArray("data");
            if (dataArr == null || dataArr.size() == 0) {
                break; // 无数据时终止分页
            }

            List<T> pageData = dataArr.toJavaList(clazz);
            allData.addAll(pageData);
            log.debug("第 {} 页数据解析完成，新增 {} 条", page, pageData.size());

            // 检查是否还有下一页
            int totalPages = result.getIntValue("max_page");
            if (page >= totalPages) {
                break;
            }
            page++;
        }
        return allData;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncAll() throws Exception {
        List<DataConvertError> convertErrors = new ArrayList<>();

        try {
            log.info("===== 开始执行BK数据同步 =====");

            // 1. 获取access_token
            String token = getAccessTokenWithRetry(3);
            log.info("成功获取token（脱敏显示）: {}", maskToken(token));

            // 2. 获取所有原始数据（学生基本信息、学籍、专业、班级）
            List<BKApiDTO> basicStudentList = getAllApiData(token, DATA_URL, BKApiDTO.class); // 原学生基本信息接口
            List<StudentStatusDTO> statusList = getAllApiData(token, STUDENT_STATUS_URL, StudentStatusDTO.class); // 学籍信息
            List<MajorDTO> majorList = getAllApiData(token, MAJOR_URL, MajorDTO.class); // 专业信息
            List<ClassDTO> classList = getAllApiData(token, CLASS_URL, ClassDTO.class); // 班级信息

            log.info("数据获取完成：学生基本信息 {} 条，学籍 {} 条，专业 {} 条，班级 {} 条",
                    basicStudentList.size(), statusList.size(), majorList.size(), classList.size());

            // 3. 构建关联映射（提高查询效率）
            // 3.1 学籍信息：学号 -> 专业编码+班级编码（Map<XH, StudentStatusDTO>）
            Map<String, StudentStatusDTO> xhToStatusMap = statusList.stream()
                    .collect(Collectors.toMap(StudentStatusDTO::getXH, s -> s, (existing, replacement) -> existing));

            // 3.2 专业信息：校内专业编码（XNZYBM） -> 专业名称（优先ZYMC，备选XNZYMC，补充特殊编码）
            Map<String, String> zybmToNameMap = majorList.stream()
                    .filter(m -> m.getXNZYBM() != null)
                    .map(m -> {
                        String majorName = m.getXNZYMC() != null ? m.getXNZYMC().trim()
                                : (m.getXNZYMC() != null ? m.getXNZYMC().trim() : null);
                        return new AbstractMap.SimpleEntry<>(m.getXNZYBM().trim(), majorName);
                    })
                    .filter(entry -> entry.getValue() != null)
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (existing, replacement) -> existing));

            // 手动添加特殊编码的默认专业名称
            zybmToNameMap.put("9903", "特殊状态");
            zybmToNameMap.put("9902", "特殊状态");
            zybmToNameMap.put("9901", "特殊状态");
            // 3.3 班级信息：班级编码 -> 班级名称（补充特殊编码）
            Map<String, String> bjbmToNameMap = classList.stream()
                    .filter(c -> c.getBJBM() != null && c.getBJMC() != null)
                    .collect(Collectors.toMap(
                            ClassDTO::getBJBM,
                            ClassDTO::getBJMC,
                            (existing, replacement) -> existing));

            // 手动添加特殊班级的默认名称
            bjbmToNameMap.put("交换生期满", "交换生（期满）");
            bjbmToNameMap.put("退学", "退学学生");
            bjbmToNameMap.put("休学班", "休学学生");
            bjbmToNameMap.put("未报到", "未报到学生");
            bjbmToNameMap.put("参军入伍", "参军入伍学生");
            bjbmToNameMap.put("死亡", "特殊状态（死亡）");
            bjbmToNameMap.put("电化1205", "历史班级（电化1205）"); // 历史班级保留原始名称
            bjbmToNameMap.put("电化1303", "历史班级（电化1303）");
            // 4. 转换数据并关联专业和班级名称
            List<BKStudent> validStudents = convertAndLinkData(basicStudentList, xhToStatusMap, zybmToNameMap,
                    bjbmToNameMap, convertErrors);
            log.info("数据转换完成: 有效数据 {} 条，异常数据 {} 条", validStudents.size(), convertErrors.size());

            // 5. 处理异常数据
            handleConvertErrors(convertErrors);

            // 6. 清空旧数据并插入新数据（原有逻辑不变）
            studentMapper.deleteAll();
            studentMapper.resetAutoIncrement();
            log.info("已清空历史数据");

            if (!validStudents.isEmpty()) {
                int batchSize = 100;
                batchInsertStudents(validStudents, batchSize);
                log.info("新数据插入完成: 累计插入 {} 条", validStudents.size());
            } else {
                log.warn("无有效数据可插入");
            }

            log.info("===== BK数据同步成功完成 =====");

        } catch (Exception e) {
            log.error("===== BK数据同步失败 =====", e);
            throw e;
        }
    }

    /**
     * 获取access_token（带重试机制）
     */
    private String getAccessTokenWithRetry(int maxRetry) throws Exception {
        int retryCount = 0;
        while (retryCount < maxRetry) {
            try {
                return getAccessToken();
            } catch (Exception e) {
                retryCount++;
                if (retryCount >= maxRetry) {
                    log.error("获取access_token重试 {} 次后失败", maxRetry, e);
                    throw new Exception("获取接口授权失败，已达最大重试次数", e);
                }
                log.warn("获取access_token第 {} 次失败，1秒后重试", retryCount, e);
                TimeUnit.SECONDS.sleep(1); // 重试间隔1秒
            }
        }
        throw new Exception("未获取到access_token");
    }

    /**
     * 原始获取access_token方法
     */
    private String getAccessToken() throws Exception {
        // 缓存未过期则直接返回
        if (token != null && LocalDateTime.now().isBefore(tokenExpireTime)) {
            return token;
        }

        // 重新获取token
        String resp = HttpUtils.sendGet(TOKEN_URL + "?key=" + KEY + "&secret=" + SECRET);
        log.debug("token接口响应原始内容：{}", resp); // 调试日志

        JSONObject json = JSONObject.parseObject(resp);
        if (!"10000".equals(json.getString("code"))) {
            throw new RuntimeException("获取token失败：" + json.getString("msg"));
        }

        JSONObject result = json.getJSONObject("result");
        this.token = result.getString("access_token");
        this.tokenExpireTime = LocalDateTime.now().plusHours(1); // 假设有效期1小时
        return this.token;
    }

    /**
     * 分页获取所有数据
     */
    private List<BKApiDTO> getAllData(String token) throws Exception {
        List<BKApiDTO> allData = new ArrayList<>();
        int page = 1;

        while (true) {
            String url = DATA_URL + "?access_token=" + token + "&page=" + page;
            String resp = HttpUtils.sendGet(url);
            log.debug("第 {} 页数据接口响应：{}", page, resp); // 调试日志

            JSONObject json = JSONObject.parseObject(resp);
            if (!"10000".equals(json.getString("code"))) {
                throw new RuntimeException("第" + page + "页数据获取失败：" + json.getString("msg"));
            }

            JSONObject result = json.getJSONObject("result");
            JSONArray dataArr = result.getJSONArray("data");
            if (dataArr == null || dataArr.size() == 0) {
                break; // 无数据时终止分页
            }

            // 转换当前页数据
            List<BKApiDTO> pageData = dataArr.toJavaList(BKApiDTO.class);
            allData.addAll(pageData);
            log.debug("第 {} 页数据解析完成，新增 {} 条", page, pageData.size());

            // 检查是否还有下一页
            int totalPages = result.getIntValue("max_page");
            if (page >= totalPages) {
                break;
            }

            page++;
        }

        return allData;
    }

    /**
     * 转换API数据为实体（核心：异常检测）
     */
    /**
     * 转换学生基本信息并关联专业、班级名称
     */
    private List<BKStudent> convertAndLinkData(
            List<BKApiDTO> basicList,
            Map<String, StudentStatusDTO> xhToStatusMap,
            Map<String, String> zybmToNameMap,
            Map<String, String> bjbmToNameMap,
            List<DataConvertError> errors) {

        List<BKStudent> students = new ArrayList<>();
        DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyyMMdd");

        for (BKApiDTO basic : basicList) {
            String xh = basic.getXH();
            if (xh == null || xh.trim().isEmpty()) {
                errors.add(createError(null, "stuSno", null, "学号为空（必填项缺失）"));
                continue;
            }
            xh = xh.trim();

            // 转换基本信息
            BKStudent student = new BKStudent();
            student.setStuSno(xh);
            student.setStuName(basic.getXM() != null ? basic.getXM().trim() : null);
            student.setStuSex(basic.getXBM());
            // 出生日期转换（CSRQ -> stuBirthday）
            if (basic.getCSRQ() != null && !basic.getCSRQ().trim().isEmpty()) {
                String csrq = basic.getCSRQ().trim();
                // 先校验长度（yyyyMMdd格式应为8位）
                if (csrq.length() != 8) {
                    errors.add(createError(xh, "stuBirth", csrq, "出生日期格式错误（长度应为8位，应为yyyyMMdd）"));
                } else {
                    try {
                        LocalDate birthDate = LocalDate.parse(csrq, dateFmt);
                        LocalDateTime birthDateTime = birthDate.atStartOfDay();
                        student.setStuBirth(birthDateTime);
                    } catch (DateTimeParseException e) {
                        errors.add(createError(xh, "stuBirth", csrq, "出生日期格式错误（应为yyyyMMdd，解析失败）"));
                    } catch (Exception e) {
                        errors.add(createError(xh, "stuBirth", csrq, "出生日期转换发生未知错误"));
                    }
                }
            } else {
                student.setStuBirth(null); // 空值处理
            }

            // 民族码转换（MZM -> stuNation）
            student.setStuNation(basic.getMZM() != null ? basic.getMZM().trim() : null);

            // 政治面貌码转换（ZZMMM -> stuPartyType）
            student.setStuPartyType(basic.getZZMMM() != null ? basic.getZZMMM().trim() : null);

            // 通讯地址转换（TXDZ -> stuHomeAddress）
            student.setStuHomeAddress(basic.getTXDZ() != null ? basic.getTXDZ().trim() : null);

            // 电话转换（LXDH -> stuTelephone）
            String telephone = basic.getLXDH() != null ? basic.getLXDH().trim() : null;
            if (telephone != null) {
                // 超过20位则截断（与数据库字段长度保持一致）
                if (telephone.length() > 25) {
                    telephone = telephone.substring(0, 20);
                    log.warn("学号[{}]电话号码过长，已截断为20位: {}", xh, telephone);
                }
            }
            student.setStuTelephone(telephone);

            // 邮箱转换（DZYX -> stuEmail）
            student.setStuEmail(basic.getDZYX() != null ? basic.getDZYX().trim() : null);

            // 籍贯转换（JGMC -> stuPlaceOfOrigin）
            student.setStuPlaceOfOrigin(basic.getJGMC() != null ? basic.getJGMC().trim() : null);

            // 关联学籍信息（获取专业编码和班级编码）
            StudentStatusDTO status = xhToStatusMap.get(xh);
            if (status == null) {
                errors.add(createError(xh, "studentStatus", xh, "未查询到对应学籍信息"));
            } else {
                // 关联专业名称
                String zybm = status.getZYBM();
                if (zybm != null) {
                    String majorName = zybmToNameMap.get(zybm);
                    student.setStuMajor(majorName);
                    if (majorName == null) {
                        errors.add(createError(xh, "stuMajor", zybm, "未查询到对应专业名称（专业编码：" + zybm + "）"));
                    }
                }

                // 关联班级名称
                String bjbm = status.getBJBM();
                if (bjbm != null) {
                    String className = bjbmToNameMap.get(bjbm);
                    student.setStuClass(className);
                    if (className == null) {
                        errors.add(createError(xh, "stuClass", bjbm, "未查询到对应班级名称（班级编码：" + bjbm + "）"));
                    }
                }
            }

            students.add(student);
        }

        return students;
    }

    /**
     * 分批次插入学生数据
     */
    private void batchInsertStudents(List<BKStudent> students, int batchSize) {
        for (int i = 0; i < students.size(); i += batchSize) {
            int end = Math.min(i + batchSize, students.size());
            List<BKStudent> batch = students.subList(i, end);
            studentMapper.batchInsert(batch);
            log.debug("已插入第 {} - {} 条数据", i + 1, end);
        }
    }

    /**
     * 处理转换异常数据（打印+保存文件）
     */
    private void handleConvertErrors(List<DataConvertError> errors) {
        if (errors.isEmpty()) {
            return;
        }

        // 1. 打印异常汇总日志
        log.warn("===== 数据转换异常汇总（共 {} 条） =====", errors.size());
        errors.forEach(error -> log.warn("学号: {} | 异常字段: {} | 异常值: {} | 原因: {}",
                error.getStuSno(),
                error.getFieldName(),
                truncateValue(error.getFieldValue(), 50), // 截断超长值
                error.getErrorReason()));

        // 2. 保存异常数据到本地文件
        try {
            saveErrorsToFile(errors);
            log.info("异常数据已保存到本地日志文件");
        } catch (IOException e) {
            log.error("保存异常数据文件失败", e); // 不阻断主流程
        }
    }

    /**
     * 保存异常数据到本地文件
     */
    private void saveErrorsToFile(List<DataConvertError> errors) throws IOException {
        String fileName = "bk_sync_errors_" + LocalDate.now() + ".log";
        try (FileWriter writer = new FileWriter(fileName, true)) { // 追加模式
            for (DataConvertError error : errors) {
                String logLine = String.format(
                        "[%s] 学号: %s, 字段: %s, 异常值: %s, 原因: %s%n",
                        LocalDateTime.now(),
                        error.getStuSno(),
                        error.getFieldName(),
                        error.getFieldValue(),
                        error.getErrorReason());
                writer.write(logLine);
            }
        }
    }

    /**
     * 工具方法：创建异常记录
     */
    private DataConvertError createError(String stuSno, String fieldName, String fieldValue, String errorReason) {
        DataConvertError error = new DataConvertError();
        error.setStuSno(stuSno);
        error.setFieldName(fieldName);
        error.setFieldValue(fieldValue);
        error.setErrorReason(errorReason);
        return error;
    }

    /**
     * 工具方法：脱敏token（隐藏中间部分）
     */
    private String maskToken(String token) {
        if (token == null || token.length() <= 8) {
            return token;
        }
        return token.substring(0, 4) + "***" + token.substring(token.length() - 4);
    }

    /**
     * 工具方法：截断超长字符串（日志显示用）
     */
    private String truncateValue(String value, int maxLength) {
        if (value == null || value.length() <= maxLength) {
            return value;
        }
        return value.substring(0, maxLength) + "...[省略" + (value.length() - maxLength) + "字符]";
    }
}